package msu.ml.core;

import msu.ml.data.*;
import weka.classifiers.*;
import java.util.*;
import javax.swing.*;

/**
 * The Experiment Class represents a basic experiment
 * where a classifier is trained with training data and
 * used to classify target data. This data is provided by
 * a data provider and IDatapostProcessors are used to
 * do something meaningful with the results.
 *
 * @author Reginald M Mead
 * @version 1.0
 */
public class Experiment 
{

    protected Classifier classifier;
    protected IDataProvider dataProvider;
    protected ArrayList<IDataPostProcessor> postProcessors;
    protected ArrayList<IDataPreProcessor> preProcessors;
    protected String name;

    /**
     * Constructs a new Experiment
     */
    public Experiment()
    {
        postProcessors = new ArrayList<IDataPostProcessor>();
        preProcessors = new ArrayList<IDataPreProcessor>();
    }

    /**
     * Add an IDataPostProcessor to this experiment
     *
     * @param processor new IDataPostProcessor
     */
    public void addPostProcessor(IDataPostProcessor processor)
    {
        postProcessors.add(processor);
    }

    /**
     * Add an IDataPostProcessor to this experiment
     *
     * @param processor new IDataPostProcessor
     */
    public void addPreProcessor(IDataPreProcessor processor)
    {
        preProcessors.add(processor);
    }

    public ArrayList<IDataPostProcessor> getDatapostProcessors()
    {
        return this.postProcessors;
    }

    public ArrayList<IDataPreProcessor> getDataPreProcessors()
    {
        return this.preProcessors;
    }

    /**
     * Gets the current classifier being used in this experiment
     *
     * @return current Classifier
     */
    public Classifier getClassifier()
    {
        return classifier;
    }

    /**
     * Gets the current Data Provider being used in this experiment
     *
     * @return current Data Provider
     */
    public IDataProvider getDataProvider()
    {
        return dataProvider;
    }

    public String getName()
    {
        return this.name;
    }

    public void trainClassifier(Classifier classifier, IDataProvider dataProvider) throws Exception
    {
        NxInstances data = dataProvider.getNextTrainingData();
        classifier.buildClassifier(data);
    }

    /**
     * Run this Experiment
     */
    public void execute()
    {
        try
        {
            for(IDataPreProcessor processor : preProcessors)
                dataProvider.addFilter(processor);
            dataProvider.initialize();

            for(IDataPostProcessor processor : postProcessors)
                processor.beginExperiment(this);
            while(dataProvider.hasMoreData())
            {
                System.out.println("Building Classifier");
                long startTime = System.currentTimeMillis();
                trainClassifier(classifier, dataProvider);
                double elapsedTime = (double)(System.currentTimeMillis() - startTime) / 1000.0;
                System.out.println("Classifier Training Complete");
                System.out.println("Duration of classifier training: " + elapsedTime + "s");
                String [] trainingFiles = dataProvider.getNextTrainingLabels();
                for(IDataPostProcessor processor : postProcessors)
                    processor.beginTraining(trainingFiles);

                
                System.out.println("Starting classification");
                startTime = System.currentTimeMillis();
                for(NxInstances instances : dataProvider.getNextTargetData())
                {
                    for(IDataPostProcessor processor : postProcessors)
                        processor.beginInstances(instances.getSourceFile());

                    for(int i = 0; i < instances.numInstances(); i++)
                    {
                        double [] distribution = classifier.distributionForInstance(
                            instances.instance(i));
                        for(IDataPostProcessor processor : postProcessors)
                            processor.process(instances.instance(i), distribution);
                    }

                    for(IDataPostProcessor processor : postProcessors)
                        processor.endInstances();
                }
                elapsedTime = (double)(System.currentTimeMillis() - startTime) / 1000.0;
                System.out.println("Classification Complete");
                System.out.println("Duration of classification: " + elapsedTime + "s");
            }
            for(IDataPostProcessor processor : postProcessors)
                processor.endExperiment();
        }
        catch(Exception e)
        {
            System.out.println(e);
            e.printStackTrace();
        }
    }

    /**
     * Set the classifier to use in this experiment
     *
     * @param newVal Classifier to use in this experiment
     */
    public void setClassifier(Classifier newVal)
    {
        classifier = newVal;
    }

    /**
     * Set the DataProvider to use in this experiment
     *
     * @param newVal DataProvider to use in this experiment
     */
    public void setDataProvider(IDataProvider newVal)
    {
        dataProvider = newVal;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    private HashSet<String> features;
    public void setFeatures(String value)
    {
        this.setFeatures(new HashSet<String>(
            Arrays.asList(value.split(",\\s*"))));
    }

    public void setFeatures(HashSet<String> value)
    {
        this.features = value;
    }

    public HashSet<String> getFeatures()
    {
        return this.features;
    }


}
